﻿/******************************************************************************
	Cobalt - utility libraries for building XNA games
	Copyright (C) 2008 Philip Djonov

	This program is free software; you can redistribute it and/or modify it
	under the terms of the GNU General Public License as published by the Free
	Software Foundation; either version 2 of the License, or (at your option)
	any later version.

	This program is distributed in the hope that it will be useful, but
	WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
	or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
	for more details.

	You should have received a copy of the GNU General Public License along
	with this program; if not, write to the
	
		Free Software Foundation, Inc.
		51 Franklin Street, Fifth Floor
		Boston, MA  02110-1301, USA.
******************************************************************************/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Cobalt.Framework.Console
{

	using UI;
	using Cobalt.Framework.Graphics;

	public sealed class ConsoleVariablesUI : Element
	{
		private Console console;

		public ConsoleVariablesUI( Console console, UiContext context )
			: base( context )
		{
			if( console == null )
				throw new ArgumentNullException( "console" );

			this.console = console;

			CreateUI();
		}

		private sealed class VariableEdit : Element
		{
			public VariableEdit( UiContext context )
				: base( context )
			{
				TextSize.SetSimpleValue( 12 );
			}

			public Property<Font> Font;
			public Property<float> TextSize;
			public Property<Vector2> Size;
			public Property<Variable> Variable;
			public Property<Color> Color;
			public Property<Color> HighlightColor;

			private Variable editVar;

			private int iVal;
			private float fVal;
			private Vector3 v3Val;

			private int iComp, nComps;

			private void BeginEditing()
			{
				var var = Variable.Evaluate();

				if( var == null )
					return;

				if( var is Variable<bool> )
				{
					var bVar = var as Variable<bool>;
					bVar.Value = !bVar.Value;
					return;
				}

				iComp = 0;
				nComps = 1;

				if( var is Variable<int> )
				{
					iVal = (var as Variable<int>).Value;
				}
				else if( var is Variable<float> )
				{
					fVal = (var as Variable<float>).Value;
				}
				else if( var is Variable<Vector3> )
				{
					v3Val = (var as Variable<Vector3>).Value;
					nComps = 3;
				}

				editVar = var;
			}

			public void EndEditing( bool cancel )
			{
				if( editVar == null )
					return;

				if( cancel )
				{
					if( editVar is Variable<int> )
						(editVar as Variable<int>).Value = iVal;
					else if( editVar is Variable<float> )
						(editVar as Variable<float>).Value = fVal;
					if( editVar is Variable<Vector3> )
						(editVar as Variable<Vector3>).Value = v3Val;
				}

				editVar = null;
			}

			private void Adjust( int direction )
			{
				if( editVar != Variable.Evaluate() )
				{
					EndEditing( true );
					return;
				}

				if( editVar is Variable<int> )
				{
					(editVar as Variable<int>).Value += direction;
				}
				else if( editVar is Variable<float> )
				{
					(editVar as Variable<float>).Value += direction * 0.1F;
				}
				else if( editVar is Variable<Vector3> )
				{
					var var = (editVar as Variable<Vector3>);

					Vector3 delta = new Vector3();

					switch( iComp )
					{
					case 0:
						delta.X = direction * 0.1F;
						break;

					case 1:
						delta.Y = direction * 0.1F;
						break;

					case 2:
						delta.Z = direction * 0.1F;
						break;
					}

					var.Value += delta;
				}
			}

			protected internal override void OnHandleEvent( IEventRoutingContext context )
			{
				var vbe = context.Event as VirtualButtonEvent;
				if( vbe == null )
					return;

				switch( vbe.Button )
				{
				case VirtualButton.A:
					if( editVar != null )
						EndEditing( false );
					else
						BeginEditing();

					context.MarkHandled();
					break;

				case VirtualButton.B:
					if( editVar != null )
					{
						EndEditing( true );
						context.MarkHandled();
					}
					break;

				case VirtualButton.Y:
					editVar = null;
					{
						var var = Variable.Evaluate();
						if( var != null )
							var.Reset();
					}
					context.MarkHandled();
					break;

				case VirtualButton.Up:
					if( editVar != null )
					{
						Adjust( 1 );
						context.MarkHandled();
					}
					break;

				case VirtualButton.Down:
					if( editVar != null )
					{
						Adjust( -1 );
						context.MarkHandled();
					}
					break;

				case VirtualButton.Left:
					if( editVar != null )
					{
						if( iComp > 0 )
							iComp--;
						context.MarkHandled();
					}
					break;
					
				case VirtualButton.Right:
					if( editVar != null )
					{
						if( iComp < nComps - 1 )
							iComp++;
						context.MarkHandled();
					}
					break;
				}
			}

			protected internal override void Render( IRenderContext context )
			{
				var var = Variable.Evaluate();

				if( var == null )
					return;

				var size = Size.Evaluate();

				var layout = UiContext.TextLayout;

				var textbuf = UiContext.TextBuffer;

				var cl = Color.Evaluate();
				var hcl = HighlightColor.Evaluate();

				layout.Font = Font.Evaluate() ?? UiContext.DefaultFont;
				layout.TextSize = TextSize.Evaluate();

				layout.HorizontalPlacement = TextPlacement.Far;
				layout.VerticalPlacement = TextPlacement.Center;
				layout.ClippingMode = TextClippingMode.None;

				if( var is Variable<bool> )
				{
					layout.Reset();
					layout.LayoutLines( (var as Variable<bool>).Value ? "true" : "false", new Point(), size );
					DrawLayout( context, layout, cl );
					
					return;
				}

				if( var is Variable<int> )
				{
					textbuf.Clear();
					textbuf.AppendInt( (var as Variable<int>).Value, 0, ' ' );

					layout.Reset();
					layout.LayoutLines( textbuf, new Point(), size );
					DrawLayout( context, layout, var == editVar ? hcl : cl );

					return;
				}

				if( var is Variable<float> )
				{
					textbuf.Clear();
					textbuf.AppendFloat( (var as Variable<float>).Value, 0, ' ', 3 );

					layout.Reset();
					layout.LayoutLines( textbuf, new Point(), size );
					DrawLayout( context, layout, var == editVar ? hcl : cl );

					return;
				}

				if( var is Variable<Vector3> )
				{
					var v3var = var as Variable<Vector3>;

					var or = new Point();
					Rectangle bd;

					textbuf.Clear();
					textbuf.AppendFloat( v3var.Value.Z, 0, ' ', 3 );

					layout.Reset();
					layout.LayoutLines( textbuf, or, size );
					DrawLayout( context, layout, (var == editVar && iComp == 2) ? hcl : cl );

					bd = layout.GetLayoutBounds();
					or.X -= bd.Width + 8;

					textbuf.Clear();
					textbuf.AppendFloat( v3var.Value.Y, 0, ' ', 3 );

					layout.Reset();
					layout.LayoutLines( textbuf, or, size );
					DrawLayout( context, layout, (var == editVar && iComp == 1) ? hcl : cl );

					bd = layout.GetLayoutBounds();
					or.X -= bd.Width + 8;

					textbuf.Clear();
					textbuf.AppendFloat( v3var.Value.X, 0, ' ', 3 );

					layout.Reset();
					layout.LayoutLines( textbuf, or, size );
					DrawLayout( context, layout, (var == editVar && iComp == 0) ? hcl : cl );

					return;
				}

				layout.LayoutLines( var.ToString(), new Point(), size );
				DrawLayout( context, layout, cl );
			}

			private void DrawLayout( IRenderContext context, TextLayout layout, Color cl )
			{
				layout.BeginGlyphLayout();

				TextLayout.GlyphLayout glyph;
				while( layout.LayoutNextGlyph( out glyph ) )
				{
					context.Draw( glyph.Sheet, glyph.Origin, glyph.SourceRect,
						cl, 0, Vector2.Zero, glyph.Scale, SpriteEffects.None );
				}
			}
		}

		private List groupsList, varList;
		private VariableEdit varEdit;

		private int selectedGroup, selectedVar;

		private void CreateUI()
		{
			//pick out some colors
			Color clBackground = new Color( 0x40, 0x40, 0x40, 0xFF );
			Color clAltBackground = new Color( 0x50, 0x50, 0x50, 0xFF );
			Color clTitle = new Color( 0xFF, 0xA5, 0x00, 0xFF );
			Color clText = new Color( 0xCC, 0xCC, 0xCC, 0xFF );
			Color clActiveBg = new Color( 0x80, 0x80, 0x80, 0xFF );
			Color clHighlight = new Color( 0x9A, 0xCD, 0x32, 0xFF );

			groupsList = new List( UiContext );

			groupsList.Items.SetSimpleValue( console.VariableGroups );

			groupsList.LayoutCount.SetSimpleValue( 1 );
			groupsList.LayoutDisplacement.SetSimpleValue( new Vector2( 400, 0 ) );

			var groupTemplate = new Group( UiContext );
			groupsList.Template = groupTemplate;

			groupTemplate.HandleEvent += ( s, e ) =>
				{
					if( e.IsHandled )
						return;

					var vbe = e.Event as VirtualButtonEvent;
					if( vbe != null )
					{
						switch( vbe.Button )
						{
						case VirtualButton.Left:
							SelectGroup( selectedGroup - 1 );
							e.MarkHandled();
							break;

						case VirtualButton.Right:
							SelectGroup( selectedGroup + 1 );
							e.MarkHandled();
							break;

						default:
							return;
						}
					}
				};

			//populate the groupsList template

			//create the heading
			var groupHeadingBg = new ColorFill( UiContext );
			groupTemplate.Children.Add( groupHeadingBg );

			groupHeadingBg.Size.SetSimpleValue( new Vector2( 400, 28 ) );
			groupHeadingBg.Color.SetSimpleValue( clBackground );

			var groupHeading = new Label( UiContext );
			groupTemplate.Children.Add( groupHeading );

			groupHeading.Origin.SetSimpleValue( new Vector2( 32, 0 ) );
			groupHeading.Size.SetSimpleValue( new Vector2( 0, 28 ) );
			groupHeading.VerticalPlacement.SetSimpleValue( TextPlacement.Center );
			groupHeading.TextSize.SetSimpleValue( 16 );
			groupHeading.Color.SetSimpleValue( clTitle );
			groupHeading.Text.SetDynamicValue( () => (groupTemplate.GetRoutingMeta() as VariableGroup).Name );

			var prevGroupArrow = new Image( UiContext, UiContext.DefaultSprites, "Arrow" );
			groupTemplate.Children.Add( prevGroupArrow );

			prevGroupArrow.Origin.SetSimpleValue( new Vector2( 4, 6 ) );
			prevGroupArrow.Visible.SetDynamicValue( () => selectedGroup > 0 );
			prevGroupArrow.SpriteEffects.SetSimpleValue( SpriteEffects.FlipHorizontally );
			prevGroupArrow.Color.SetSimpleValue( clTitle );

			var nextGroupArrow = new Image( UiContext, UiContext.DefaultSprites, "Arrow" );
			groupTemplate.Children.Add( nextGroupArrow );

			nextGroupArrow.Origin.SetSimpleValue( new Vector2( 380, 6 ) );
			nextGroupArrow.Visible.SetDynamicValue( () => selectedGroup < console.VariableGroups.Count - 1 );
			nextGroupArrow.Color.SetSimpleValue( clTitle );

			//create the list
			varList = new List( UiContext );
			groupTemplate.Children.Add( varList );

			varList.Items.SetDynamicValue( () => groupTemplate.GetRoutingMeta() as VariableGroup );

			varList.Origin.SetSimpleValue( new Vector2( 0, 28 ) );
			varList.LayoutDisplacement.SetSimpleValue( new Vector2( 0, 24 ) );
			varList.LayoutCount.SetSimpleValue( 10 );

			var varTemplate = new Group( UiContext );
			varList.Template = varTemplate;

			//set up the template
			varTemplate.HandleEvent += ( s, e ) =>
				{
					if( e.IsHandled )
						return;

					var veb = e.Event as VirtualButtonEvent;
					if( veb != null )
					{
						switch( veb.Button )
						{
						case VirtualButton.Up:
							SelectVariable( selectedVar - 1 );
							e.MarkHandled();
							break;

						case VirtualButton.Down:
							SelectVariable( selectedVar + 1 );
							e.MarkHandled();
							break;

						default:
							return;
						}
					}
				};

			//get a background color going
			var varBg = new ColorFill( UiContext );
			varTemplate.Children.Add( varBg );

			varBg.Size.SetSimpleValue( new Vector2( 400, 24 ) );
			varBg.Color.SetDynamicValue( () =>
			{
				if( varList.GroupElement.ContainsFocus() )
					return clActiveBg;

				int idx = varList.FetchTraversalIndex();
				return (idx & 0x1) != 0 ? clBackground : clAltBackground;
			} );

			//set up the console variable template
			var varName = new Label( UiContext );
			varTemplate.Children.Add( varName );

			varName.Origin.SetSimpleValue( new Vector2( 8, 0 ) );
			varName.Size.SetSimpleValue( new Vector2( 0, 28 ) );
			varName.VerticalPlacement.SetSimpleValue( TextPlacement.Center );
			varName.Text.SetDynamicValue( () => (varTemplate.GetRoutingMeta() as Variable).Name );
			varName.TextSize.SetSimpleValue( 12 );
			varName.Color.SetSimpleValue( clText );

			varEdit = new VariableEdit( UiContext );
			varTemplate.Children.Add( varEdit );

			varEdit.Origin.SetSimpleValue( new Vector2( 250, 0 ) );
			varEdit.Size.SetSimpleValue( new Vector2( 146, 24 ) );
			varEdit.Variable.SetDynamicValue( () => varTemplate.GetRoutingMeta() as Variable );

			varEdit.Color.SetSimpleValue( clText );
			varEdit.HighlightColor.SetSimpleValue( clHighlight );
		}

		private Dictionary<VariableGroup, int> groupSelection
			= new Dictionary<VariableGroup, int>();

		private void SelectGroup( int groupNum )
		{
			/*
			while( groupNum < 0 )
				groupNum += console.VariableGroups.Count;

			while( groupNum >= console.VariableGroups.Count )
				groupNum -= console.VariableGroups.Count;
			*/
			
			groupNum = Helpers.Clamp( groupNum, 0, console.VariableGroups.Count - 1 );

			if( groupNum == selectedGroup )
				return;

			selectedGroup = groupNum;

			if( !groupSelection.TryGetValue( console.VariableGroups[groupNum], out selectedVar ) )
				selectedVar = 0;

			ResetSelectionPath();

			varList.ScrollIntoView( selectedVar, true );
			groupsList.ScrollTo( selectedGroup, false );
		}

		private void SelectVariable( int varNum )
		{
			var group = console.VariableGroups[selectedGroup];
			var groupCount = group.Count;

			while( varNum < 0 )
				varNum += groupCount;

			while( varNum >= groupCount )
				varNum -= groupCount;

			varNum = Helpers.Clamp( varNum, 0, groupCount - 1 );

			if( varNum == selectedVar )
				return;

			selectedVar = varNum;
			groupSelection[group] = selectedVar;

			varList.ScrollIntoView( selectedVar, false );

			ResetSelectionPath();
		}

		private void ResetSelectionPath()
		{
			varEdit.EndEditing( true );

			var focusPath = UiContext.FocusedPath;

			focusPath.Clear();

			focusPath.Push( this );
			focusPath.PushSelector( groupsList, selectedGroup );
			focusPath.PushSelector( varList, selectedVar );
			focusPath.Push( varEdit );
		}

		protected internal override void OnRootStateChanged( RootState state )
		{
			base.OnRootStateChanged( state );

			if( state.IsVisible )
			{
				groupsList.ScrollTo( selectedGroup, true );
			}
			else
			{
				varEdit.EndEditing( true );
			}
			
			if( state.IsTopmost )
			{
				ResetSelectionPath();
			}
		}

		protected internal override void TraverseToRender( ITraversalContext context )
		{
			context.Traverse( groupsList );
		}

	}

}